package com.onadake.utils;

import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;

/**
 * This class contains the static methods which simplifies the validation of the
 * simple fields and properties.
 * <p>
 * </p>
 * Methods with pattern guardXXX guards code to pass function if tested variable
 * does not particular criteria. In that case IllegalArgumentException is
 * thrown. If variable satisfy the criteria, then function does nothing.
 * 
 * @author radek.hecl
 * 
 */
public class ValidationUtils {

    /**
     * Guards object to be null.
     * 
     * @param obj tested object
     * @param msg message to appear in the exception
     */
    public static void guardNull(Object obj, String msg) {
        if (obj != null) {
            throw new IllegalArgumentException("guardNull failed for " + obj + ": " + msg);
        }
    }

    /**
     * Guards object to be not null.
     * 
     * @param obj tested object
     * @param msg message to appear in the exception
     */
    public static void guardNotNull(Object obj, String msg) {
        if (obj == null) {
            throw new IllegalArgumentException("guardNotNull failed: " + msg);
        }
    }

    /**
     * Guards string to be empty. Empty means both null and empty string.
     * 
     * @param str tested string
     * @param msg message to appear in the exception
     */
    public static void guardEmpty(String str, String msg) {
        if (StringUtils.isNotEmpty(str)) {
            throw new IllegalArgumentException("guardEmpty failed for " + str + ": " + msg);
        }
    }

    /**
     * Guards string to be not empty. Empty means both null and empty string.
     * 
     * @param str tested string
     * @param msg message to appear in the exception
     */
    public static void guardNotEmpty(String str, String msg) {
        if (StringUtils.isEmpty(str)) {
            throw new IllegalArgumentException("guardNotEmpty failed: " + msg);
        }
    }

    /**
     * Guards string to match the regular expression.
     * 
     * @param str tested string, not null
     * @param regex regular expression which needs to be satisfied
     * @param msg name of to put into exception message
     */
    public static void guardMatch(String str, String regex, String msg) {
        if (str == null) {
            throw new NullPointerException("guardMatch failed for null string with " + regex + ": " + msg);
        }

        if (!str.matches(regex)) {
            throw new IllegalArgumentException("guardMatch failed for " + str + " with " + regex + ": " + msg);
        }
    }

    /**
     * Guards that collection is not null and also any element inside is not null.
     * 
     * @param collection tested object
     * @param msg name of to put into exception message
     */
    public static <T> void guardNotNullCollection(Collection<T> collection, String msg) {
        if (collection == null) {
            throw new IllegalArgumentException("guardNotNullCollection failed for null: " + msg);
        }
        for (T elm : collection) {
            if (elm == null) {
                throw new IllegalArgumentException("guardNotNullCollection failed for " + collection + ": " + msg);
            }
        }
    }

    /**
     * Guards that collection is not null and also any element inside is not null or empty string.
     * Empty collection (size = 0) is fine.
     * 
     * @param collection tested object
     * @param msg name of to put into exception message
     */
    public static void guardNotEmptyStringInCollection(Collection<String> collection, String msg) {
        if (collection == null) {
            throw new IllegalArgumentException("guardNotEmptyStringInCollection failed for null: " + msg);
        }
        for (String elm : collection) {
            if (StringUtils.isEmpty(elm)) {
                throw new IllegalArgumentException("guardNotEmptyStringInCollection failed for " + collection + ": " + msg);
            }
        }
    }

    /**
     * Guards that map is not null and also any element inside has not empty key or value.
     * Empty map (size = 0) is fine.
     * 
     * @param map tested map
     * @param msg massage which appears in exception when validation failed
     */
    public static void guardNotEmptyStringInMap(Map<String, String> map, String msg) {
        if (map == null) {
            throw new IllegalArgumentException("guardNotEmptyStringInMap failed for null: " + msg);
        }
        for (Map.Entry<String, String> entry : map.entrySet()) {
            if (StringUtils.isEmpty(entry.getKey()) || StringUtils.isEmpty(entry.getValue())) {
                throw new IllegalArgumentException("guardNotEmptyStringInMap failed for " + map + ": " + msg);
            }
        }
    }

    
    /**
     * Guards that map is not null and also all the keys and values are not null.
     * 
     * @param map tested object
     * @param msg massage which appears in exception when validation failed
     */
    public static <K, V> void guardNotNullMap(Map<K, V> map, String msg) {
        if (map == null) {
            throw new IllegalArgumentException("guardNotNullMap failed for null: " + msg);
        }
        for (Map.Entry<K, V> entry : map.entrySet()) {
            if (entry.getKey() == null) {
                throw new IllegalArgumentException("guardNotNullMap failed for " + map + ": " + msg);
            }
            if (entry.getValue() == null) {
                throw new IllegalArgumentException("guardNotNullMap failed for " + map + ": " + msg);
            }
        }
    }

    /**
     * Guards that the specified number is 0, 1, 2...
     * 
     * @param number tested number
     */
    public static void guardNotNegativeInt(int number, String msg) {
        if (number < 0) {
            throw new IllegalArgumentException("guardNotNegativeInt failed for " + number + ": " + msg);
        }
    }
    
    /**
     * Guards that specified number is 1, 2, 3...
     * 
     * @param number tested number
     * @param msg message to appear in the exception
     */
    public static void guardPositiveInt(int number, String msg) {
        if (number < 1) {
            throw new IllegalArgumentException("guardPositiveInt failed for " + number + ": " + msg);
        }
    }

    /**
     * Guards that specified number is 1, 2, 3...
     * 
     * @param number tested number
     * @param msg message to appear in the exception
     */
    public static void guardPositiveLong(long number, String msg) {
        if (number < 1) {
            throw new IllegalArgumentException("guardPositiveLong failed for " + number + ": " + msg);
        }
    }

    /**
     * Guards long to be one non zero.
     * 
     * @param obj object to test
     * @param msg message to appear in the exception
     */
    public static void guardNotZeroLong(long obj, String msg) {
        if (obj == 0) {
            throw new IllegalArgumentException("guardNotZeroLong failed for " + obj + ": " + msg);
        }
    }

    /**
     * Guards integer to be non zero.
     * 
     * @param obj object to test
     * @param msg message to appear in the exception
     */
    public static void guardNotZeroInt(int obj, String msg) {
        if (obj == 0) {
            throw new IllegalArgumentException("guardNotZeroInt failed for " + obj + ": " + msg);
        }
    }

    /**
     * Guards that specified number is greater then 0.
     * 
     * @param number tested number
     * @param msg message to appear in the exception
     */
    public static void guardPositiveDouble(double number, String msg) {
        if (number <= 0) {
            throw new IllegalArgumentException("guardPositiveDouble failed for " + number + ": " + msg);
        }
    }
    
    /**
     * Guards number to not be POSITIVE_INFINITY, NEGATIVE_INFINITY, NaN.
     * 
     * @param number tested number
     * @param msg message to appear in the exception
     */
    public static void guardBoundedDouble(double number, String msg) {
        if (Double.isNaN(number)) {
            throw new IllegalArgumentException("guardBoundedDouble failed for " + number + ": " + msg);
        }
        if (Double.isInfinite(number)) {
            throw new IllegalArgumentException("guardBoundedDouble failed for " + number + ": " + msg);
        }
    }

    /**
     * Guards that the specified number is 0, 1, 2...
     * 
     * @param number tested number
     * @param msg message to appear in the exception
     */
    public static void guardNotNegativeLong(long number, String msg) {
        if (number < 0) {
            throw new IllegalArgumentException("guardNotNegativeLong failed for " + number + ": " + msg);
        }
    }
    
    /**
     * Guards that specified number is greater or equal to 0.
     * 
     * @param number tested number
     * @param msg message to appear in the exception
     */
    public static void guardNotNegativeDouble(double number, String msg) {
        if (number < 0) {
            throw new IllegalArgumentException("guardNotNegativeDouble failed for " + number + ": " + msg);
        }
    }

    /**
     * Guards String to be an IP address.
     * Right now this method supports only IPv4.
     * Later IPv6 can pass the validation
     * 
     * @param ip tested string to be an IP address
     * @param msg message to appear in the exception
     */
    public static void guardIpString(String ip, String msg) {
        ip = StringUtils.defaultString(ip);
        if (!ip.matches("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}")) {
            throw new IllegalArgumentException("guardIpString failed for " + ip + ": " + msg);
        }
    }

    /**
     * Guards two object to be equal. This is null safe, where two null objects
     * are considered to be equal.
     * 
     * @param obj1 object one
     * @param obj2 object two
     * @param msg message to appear in the exception
     */
    public static void guardEquals(Object obj1, Object obj2, String msg) {
        if (obj1 == obj2) {
            return;
        }
        if (obj1 == null || !obj1.equals(obj2)) {
            throw new IllegalArgumentException("guardEquals failed for " + obj1 + " and " + obj2 + ": " + msg);
        }
    }

    /**
     * Guards number1 to be greater than number2.
     * 
     * @param number1 number1 - the one which must be greater
     * @param number2 number2 - the one which must be less
     */
    public static void guardGreaterDouble(double number1, double number2, String msg) {
        if (number2 >= number1) {
            throw new IllegalArgumentException("guardGreaterDouble failed for " + number1 + " and " + number2 + ": " + msg);
        }
    }

    /**
     * Guards number1 to be greater or equal number2.
     * 
     * @param number1 number1 - the one which must be greater or equal
     * @param number2 number2 - the one which must be less or equal
     * @param msg message to appear in the exception
     */
    public static void guardGreaterOrEqualInt(int number1, int number2, String msg) {
        if (number2 > number1) {
            throw new IllegalArgumentException("guardGreaterOrEqualInt failed for " + number1 + " and " + number2 + ": " + msg);
        }
    }

    /**
     * Guards number1 to be greater or equal number2.
     * 
     * @param number1 number1 - the one which must be greater or equal
     * @param number2 number2 - the one which must be less or equal
     * @param msg message to appear in the exception
     */
    public static void guardGreaterOrEqualLong(long number1, long number2, String msg) {
        if (number2 > number1) {
            throw new IllegalArgumentException("guardGreaterOrEqualLong failed for " + number1 + " and " + number2 + ": " + msg);
        }
    }

    /**
     * Guards number1 to be greater or equal number2.
     * 
     * @param number1 number1 - the one which must be greater or equal
     * @param number2 number2 - the one which must be less or equal
     * @param msg message to appear in the exception
     */
    public static void guardGreaterOrEqualDouble(double number1, double number2, String msg) {
        if (number2 > number1) {
            throw new IllegalArgumentException("guardGreaterOrEqualDouble failed for " + number1 + " and " + number2 + ": " + msg);
        }
    }

    /**
     * Guard object to be instance of the specified class. Null is not an
     * instance of any class and if obj is null, then guard method will NOT pass.
     * 
     * @param obj tested object
     * @param clazz class which is used for the test
     * @param msg message to appear in the exception
     */
    public static void guardInstanceOf(Object obj, Class<?> clazz, String msg) {
        if (clazz == null) {
            throw new IllegalArgumentException("guardInstanceOf null failed for " + obj + ": " + msg);
        }
        if (obj == null) {
            throw new IllegalArgumentException("guardInstanceOf " + clazz.getName() + " failed for null: " + msg);
        }
        if (!clazz.isAssignableFrom(obj.getClass())) {
            throw new IllegalArgumentException("guardInstanceOf " + clazz.getName() + " failed for " + obj + ": " + msg);
        }
    }

    /**
     * Guards object to be one of the specified values.
     * 
     * @param obj object to test
     * @param allowed collection of allowed values
     * @param msg message to appear in the exception
     */
    public static void guardIn(Object obj, Collection<?> allowed, String msg) {
        if (!allowed.contains(obj)) {
            throw new IllegalArgumentException("guardIn failed for " + obj + " in " + allowed + ": " + msg);
        }
    }

    
    /**
     * Guards date object to be the date only, without time component.
     * If there is any field less significant than day, then it is wrong.
     * Also if date is null, then it is wrong.
     * 
     * @param date date object to be tested as date
     * @param msg message to appear in the exception
     */
    public static void guardTruncatedDate(Date date, String msg) {
        Date truncated = DateUtils.truncate(date, Calendar.DATE);
        if (date.getTime() != truncated.getTime()) {
            throw new IllegalArgumentException("guardTruncatedDate failed for " + date + ": " + msg);
        }
    }

    /**
     * Guards given class to have getter for the specified enumeration value.
     * First searches for getter which starting with "get".
     * If this getter is not presented, then search for the one which starting with "is".
     * If even this one doesn't exists, then it's validation error.
     * Examples of expected getters for a specified enumeration:
     * <ul>
     * <li>ID => getId(), isId()</li>
     * <li>EVENT_TIMESTAMP => getEventTimestamp(), isEventTimestamp()</li>
     * </ul>
     * Note: This method uses reflection, so speed is not great.
     * 
     * @param clazz class for which getter should be checked
     * @param enm enumeration value
     * @param msg message to appear in the exception
     */
    public static void guardGetterExistsForEnum(Class<?> clazz, Enum<?> enm, String msg) {
        if (clazz == null) {
            throw new IllegalArgumentException("guardGetterExistsForEnum failed for null and " + enm + ": " + msg);
        }
        if (enm == null) {
            throw new IllegalArgumentException("guardGetterExistsForEnum failed for " + clazz + " and null: " + msg);
        }
        String name = enm.name().toLowerCase();
        String getterBase = "";
        boolean capital = true;
        for (int i = 0; i < name.length(); ++i) {
            char c = name.charAt(i);
            if (c == '_') {
                capital = true;
            }
            else if (capital) {
                getterBase += String.valueOf(c).toUpperCase();
                capital = false;
            }
            else {
                getterBase += String.valueOf(c);
            }
        }
        try {
            clazz.getMethod("get" + getterBase);
        } catch (NoSuchMethodException e) {
            try {
                clazz.getMethod("is" + getterBase);
            } catch (NoSuchMethodException e1) {
                throw new IllegalArgumentException("guardGetterExistsForEnum failed for " + clazz + " and " + enm + ": " + msg);
            } catch (SecurityException e1) {
                throw new RuntimeException(e1);
            }
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        }
    }

}
